package com.google.code.gronono.gps.ui.panels.pages.tree;

import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.log4j.Logger;

import com.google.code.gronono.commons.exif.ExifData;
import com.google.code.gronono.commons.exif.ExifUtils;
import com.google.code.gronono.commons.i18n.BundleKey;
import com.google.code.gronono.commons.i18n.BundleName;
import com.google.code.gronono.commons.io.file.jpeg.JpegFile;
import com.google.code.gronono.gps.model.Configuration;
import com.google.code.gronono.gps.model.SortedFile;

/**
 * Arbre spécifique à l'application,
 * implémentant notamment la méthode {@link #getToolTipText(MouseEvent)}.
 */
@BundleName(value = "com.google.code.gronono.gps.gui")
public class SortedFileTree extends JTree {
	/** serialVersionUID. */
	private static final long serialVersionUID = 3157183115829229168L;
	/** Logger. */
	private static final Logger logger = Logger.getLogger(SortedFileTree.class);

	/** Formatage des dates pour les tooltip pour les fichier de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.date.format")
	private static String TOOLTIP_DATE_FORMAT;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.line.break")
	private static String TOOLTIP_LINE_BREAK;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.sep")
	private static String TOOLTIP_SEP;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.tab")
	private static String TOOLTIP_TAB;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.start")
	private static String TOOLTIP_START;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.start.file")
	private static String TOOLTIP_START_FILE;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.start.folder")
	private static String TOOLTIP_START_FOLDER;
	/** Tooltip pour les fichiers sources de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.src.file.path")
	private static String TOOLTIP_SRC_FILE_PATH;
	/** Tooltip pour les fichiers sources de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.src.file.lastmod")
	private static String TOOLTIP_SRC_FILE_LASTMOD;
	/** Tooltip pour les fichiers cibles de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.dst.file.path")
	private static String TOOLTIP_DST_FILE_PATH;
	/** Tooltip pour les fichiers cibles de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.dst.file.create")
	private static String TOOLTIP_DST_FILE_CREATE;
	/** Tooltip pour les fichiers cibles de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.dst.file.lastmod")
	private static String TOOLTIP_DST_FILE_LASTMOD;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.err.msg")
	private static String TOOLTIP_ERR_MSG;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.err.cause")
	private static String TOOLTIP_ERR_CAUSE;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif")
	private static String TOOLTIP_EXIF;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.src")
	private static String TOOLTIP_EXIF_SRC;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.dst")
	private static String TOOLTIP_EXIF_DST;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.orientation")
	private static String TOOLTIP_EXIF_ORIENTATION;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.orientation.rotate")
	private static String TOOLTIP_EXIF_ORIENTATION_ROTATE;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.date")
	private static String TOOLTIP_EXIF_DATE;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.exif.thumbnail")
	private static String TOOLTIP_EXIF_THUMBNAIL;
	/** Tooltip pour les fichiers de l'arbre. */
	@BundleKey(value = "result.page.file.tooltip.end")
	private static String TOOLTIP_END;

	/** La configuration de l'application. */
	private final Configuration configuration;
	/** Flag indiquant si l'arbre concerné est l'arbre source. */
	private boolean isSrcTree;
	/** Formateur pour l'affichage des dates. */
	private static SimpleDateFormat df;

	/**
	 * Constructeur.
	 * @param configuration La configuration de l'application.
	 * @param isSrcTree Flag indiquant si l'arbre concerné est l'arbre source.
	 */
	public SortedFileTree(final Configuration configuration, final boolean isSrcTree) {
		super();
		this.configuration = configuration;
		this.isSrcTree = isSrcTree;
		if (df == null) df = new SimpleDateFormat(TOOLTIP_DATE_FORMAT);
	}

	/* (non-Javadoc)
	 * @see javax.swing.JTree#getToolTipText(java.awt.event.MouseEvent)
	 */
	@Override
	public String getToolTipText(final MouseEvent e) {
		// Récupération du chemin associé à l'événement
		final TreePath path = getPathForLocation(e.getX(), e.getY());
		if (path != null) {
			// Récupération des noeud, datamodel et fichiers correspondant
			final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) path.getLastPathComponent();
			final SortedFile sortedFile = (SortedFile) selectedNode.getUserObject();
			// Génération du tooltip
			return generateTooltip(sortedFile);
		}
		return null;
	}

	/**
	 * Génère le texte d'info-bulle pour un noeud de l'arbre.
	 *
	 * @param sortedFile Le {@link SortedFile} correspondant au noeud survolé.
	 * @return Le texte de tooltip.
	 */
	private String generateTooltip(final SortedFile sortedFile) {
		final File srcFile = sortedFile.getSrcFile();
		final File dstFile = sortedFile.getDstFile();

		final boolean isSrcFile = (srcFile != null) && srcFile.isFile();
		final boolean isDstFile = (dstFile != null) && dstFile.isFile();

		// Initialisation du tooltip
		final StringBuilder tooltip = new StringBuilder();
		if (!isSrcFile && !isDstFile) tooltip.append(MessageFormat.format(TOOLTIP_START, TOOLTIP_START_FOLDER));
		else tooltip.append(MessageFormat.format(TOOLTIP_START, TOOLTIP_START_FILE));
		tooltip.append(TOOLTIP_LINE_BREAK);

		// Informations sur le fichier source
		if (isSrcFile || isSrcTree) {
			appendSrcFileInfos(srcFile, tooltip);
		}

		// Informations sur le fichier cible éventuel
		if (dstFile != null) {
			appendDstFileInfos(dstFile, isSrcFile, tooltip);
		}

		// Informations sur l'erreur éventuelle
		if (StringUtils.isNotBlank(sortedFile.getErrorMsg())) {
			appendErrInfos(sortedFile, tooltip);
		}

		// Informations sur les métadonnées exif éventuelles
		if (isSrcFile || isDstFile) {
			appendExifInfos(srcFile, dstFile, tooltip);
		}

		tooltip.append(TOOLTIP_LINE_BREAK);
		tooltip.append(TOOLTIP_END);

		return tooltip.toString();
	}

	/**
	 * Ajoute les infos liées au fichier source au tooltip.
	 *
	 * @param srcFile Le fichier source.
	 * @param tooltip Le tooltip à modifier.
	 */
	private void appendSrcFileInfos(final File srcFile, final StringBuilder tooltip) {
		tooltip.append(TOOLTIP_SRC_FILE_PATH).append(TOOLTIP_SEP).append(srcFile.getAbsolutePath());
		// Date de dernière modification
		tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_SRC_FILE_LASTMOD).append(TOOLTIP_SEP).append(df.format(new Date(srcFile.lastModified())));
		tooltip.append(TOOLTIP_LINE_BREAK);
	}

	/**
	 * Ajoute les infos liées au fichier cible au tooltip.
	 *
	 * @param dstFile Le fichier cible.
	 * @param isSrcFile <code>true</code> indique que fichier survolé est un fichier source.
	 * @param tooltip Le tooltip à modifier.
	 */
	private void appendDstFileInfos(final File dstFile, final boolean isSrcFile, final StringBuilder tooltip) {
		if (isSrcFile || isSrcTree) tooltip.append(TOOLTIP_LINE_BREAK); // Cas des dossiers pour l'arbre cible
		tooltip.append(TOOLTIP_DST_FILE_PATH);
		if (!dstFile.exists()) tooltip.append(TOOLTIP_DST_FILE_CREATE);
		tooltip.append(TOOLTIP_SEP).append(dstFile.getAbsolutePath());
		// Date de dernière modification éventuelle
		if (dstFile.exists()) tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_DST_FILE_LASTMOD).append(TOOLTIP_SEP).append(df.format(new Date(dstFile.lastModified())));
		tooltip.append(TOOLTIP_LINE_BREAK);
	}

	/**
	 * Ajoute les infos liées au message d'erreur au tooltip.
	 *
	 * @param sortedFile Le {@link SortedFile} associé au noeud survolé.
	 * @param tooltip Le tooltip à modifier.
	 */
	private void appendErrInfos(final SortedFile sortedFile, final StringBuilder tooltip) {
		// Message d'erreur
		tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_ERR_MSG).append(TOOLTIP_SEP).append(sortedFile.getErrorMsg());
		// Et cause éventuelle
		if (StringUtils.isNotBlank(sortedFile.getErrorCause())) {
			tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_ERR_CAUSE).append(TOOLTIP_SEP).append(sortedFile.getErrorCause());
		}
		tooltip.append(TOOLTIP_LINE_BREAK);
	}

	/**
	 * Ajoute les infos Exif (du fichier source ou du fichier cible) au tooltip.
	 *
	 * @param srcFile Le fichier source.
	 * @param dstFile Le fichier cible.
	 * @param tooltip Le tooltip à modifier.
	 */
	private void appendExifInfos(final File srcFile, final File dstFile, final StringBuilder tooltip) {
		// Lecture des métadonnées exif
		ExifData exifData = null;
		boolean isSrcExifData = true;
		// Du fichier cible si possible
		if ((dstFile != null) && !isSrcTree) {
			final JpegFile jpegDstFile = new JpegFile(dstFile);
			exifData = jpegDstFile.getExifData();
		}
		// Sinon du fichier source
		if (exifData == null) {
			final JpegFile jpegSrcFile = new JpegFile(srcFile);
			exifData = jpegSrcFile.getExifData();
		} else isSrcExifData = false;
		// Traitement des métadonnées pour ajout au tooltip
		if (exifData != null) {
			// Titre
			tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_EXIF);
			if (isSrcExifData) tooltip.append(TOOLTIP_EXIF_SRC);
			else tooltip.append(TOOLTIP_EXIF_DST);
			tooltip.append(TOOLTIP_SEP);
			// Date de prise de vue
			if (exifData.getDate() != null) {
				tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_EXIF_DATE).append(TOOLTIP_SEP).append(df.format(exifData.getDate()));
			}
			// Orientation
			if (exifData.getOrientation() != null) {
				tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_EXIF_ORIENTATION).append(TOOLTIP_SEP).append(exifData.getOrientationDescr());
				if (configuration.isRotate() && ExifUtils.needsRotation(exifData.getOrientation())) tooltip.append(TOOLTIP_EXIF_ORIENTATION_ROTATE);
			}
			// Thumbnail
			if (exifData.getThumbnail() != null) {
				// Ecriture temporaire du fichier dans io.tmpdir pour affichage
				final StringBuilder tmpDirPath = new StringBuilder(SystemUtils.JAVA_IO_TMPDIR).append(File.separator).append("gps");
				final StringBuilder thumbnailPath = new StringBuilder(tmpDirPath).append(File.separator).append("thumb_").append(srcFile.getName());
				logger.trace("Thumbnail : " + thumbnailPath + " (" + exifData.getThumbnail().length/1024 + " Ko)");
				final File tmpDir = new File(tmpDirPath.toString());
				final File thumb = new File(thumbnailPath.toString());
				try {
					if (!tmpDir.exists()) tmpDir.mkdirs();
					// Rmq: remplacement systématique du thumbnail pr le cas où la conf a changé ('redresser' activé puis désactivé par ex.)
					if (thumb.exists()) FileUtils.deleteQuietly(thumb);
					final FileOutputStream fos = new FileOutputStream(thumbnailPath.toString());
					if (configuration.isRotate()) fos.write(exifData.getRotatedThumbnail());
					else fos.write(exifData.getThumbnail());
					fos.close();
				} catch (final Exception ex) {
					logger.error("Exception : " + ex.getMessage() + (ex.getCause() != null ? ", cause : " + ex.getCause() : ""), ex);
				} finally {
					if (thumb.exists()) {
						tooltip.append(TOOLTIP_LINE_BREAK).append(TOOLTIP_TAB).append(TOOLTIP_EXIF_THUMBNAIL).append(TOOLTIP_SEP);
						tooltip.append("<img align=\"top\" src=\"file:&#47;&#47;&#47;&#47;").append(thumbnailPath.toString().replace(File.separator, "&#47;&#47;")).append("\" &#47;>");
						tooltip.append(TOOLTIP_LINE_BREAK);
						thumb.deleteOnExit();
					}
				}
			}
		}
	}
}
